package vip.zhaotao;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import org.seimicrawler.xpath.JXDocument;
import org.seimicrawler.xpath.JXNode;

import java.io.*;
import java.math.BigDecimal;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

/**
 * 天天基金
 */
@Slf4j
public class EveryDayFund {

    public static void main(String[] args) {
        // 开始时间
        long start = System.currentTimeMillis();
        // 文件保存路径
        String path = StringUtils.join(
                System.getProperty("user.home"),
                File.separator,
                "Desktop",
                File.separator,
                "混合型基金_",
                DateFormatUtils.format(new Date(), DateFormatUtils.ISO_DATE_FORMAT.getPattern()),
                ".csv"
        );
        // 写入csv文件头
        writeCsvHeaders(path, getCsvHeaders());
        // 写入csv文件内容
        writeCsvContent(path, getFundList(5000));
        // 打印程序耗时时间
        System.err.println(String.format("耗时：%.2f秒", (System.currentTimeMillis() - start) / 1000.0));
    }

    /**
     * 写csv文件头
     *
     * @param path
     * @param headers
     */
    private static void writeCsvHeaders(String path, String[] headers) {
        try {
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path), "GBK"));
            writer.write(StringUtils.join(headers, ","));
            writer.newLine();
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取csv文件头
     *
     * @return
     */
    private static String[] getCsvHeaders() {
        return new String[]{
                "编码",
                "名称",
                "近一周（%）",
                "近一月（%）",
                "近一季度（%）",
                "近半年（%）",
                "今年（%）",
                "近一年（%）",
                "近两年（%）",
                "近三年（%）",
                "近两年平均（%）",
                "近三年平均（%）",
                "规模（亿元）",
                "成立日",
                "成立天数",
                "现任基金经理",
                "起始日期",
                "任职天数",
                "任期回报（%）",
                "任职年份（年）",
                "任期年回报（%）",
                "持仓股票_1",
                "持仓股票_2",
                "持仓股票_3",
                "持仓股票_4",
                "持仓股票_5",
                "持仓股票_6",
                "持仓股票_7",
                "持仓股票_8",
                "持仓股票_9",
                "持仓股票_10"
        };
    }

    /**
     * 写入csv文件内容
     *
     * @param path
     * @param list
     */
    private static void writeCsvContent(String path, List<Fund> list) {
        try {
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path, true), "GBK"));
            StringBuilder stringBuilder = new StringBuilder();
            List<Object> objectList;
            for (Fund fund : list) {
                objectList = new ArrayList<>();
                objectList.add(fund.getCode());
                objectList.add(fund.getName());
                objectList.add(fund.getLastWeek());
                objectList.add(fund.getLastMonth());
                objectList.add(fund.getLastQuarter());
                objectList.add(fund.getLastHalfYear());
                objectList.add(fund.getThisYear());
                objectList.add(fund.getLastOneYear());
                objectList.add(fund.getLastTwoYear());
                objectList.add(fund.getLastThreeYear());
                objectList.add(fund.getLastTwoYearAvg());
                objectList.add(fund.getLastThreeYearAvg());
                FundDetail detail = fund.getDetail();
                objectList.add(detail.getScale());
                objectList.add(detail.getEstablishDay());
                objectList.add(detail.getEstablishDays());
                FundManager incumbentFundManager = detail.getIncumbentFundManager();
                objectList.add(incumbentFundManager.getName());
                objectList.add(incumbentFundManager.getTakeOfficeStartDate());
                objectList.add(incumbentFundManager.getTakeOfficeDays());
                objectList.add(incumbentFundManager.getTermOfficeReturn());
                objectList.add(incumbentFundManager.getTakeOfficeYear());
                objectList.add(incumbentFundManager.getTermOfficeYearReturn());
                objectList.addAll(detail.getTopTenStockPosition());
                stringBuilder.append(StringUtils.join(objectList, ","));
                stringBuilder.append("\n");
            }
            writer.write(stringBuilder.toString());
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取基金列表
     *
     * @param number
     * @return
     */
    private static List<Fund> getFundList(Integer number) {
        List<Fund> list = new ArrayList<>();
        Document document;
        try {
            document = Jsoup.connect(String.format("http://fund.eastmoney.com/data/FundGuideapi.aspx?dt=4&ft=hh&sd=&ed=&sc=3n&st=desc&pi=1&pn=%d&zf=diy&sh=list", number)).get();
        } catch (IOException e) {
            e.printStackTrace();
            return list;
        }
        String text = document.text();
        text = text.replace("var rankData =", "");
        JSONObject jsonObject = JSON.parseObject(text);
        JSONArray jsonArray = jsonObject.getJSONArray("datas");
        for (int i = 0; i < jsonArray.size(); i++) {
            String[] array = jsonArray.getString(i).split(",");
            Fund fund = new Fund();
            fund.setCode(array[0]);
            fund.setName(array[1]);
            fund.setLastWeek(stringToDouble(array[5]));
            fund.setLastMonth(stringToDouble(array[6]));
            fund.setLastQuarter(stringToDouble(array[7]));
            fund.setLastHalfYear(stringToDouble(array[8]));
            fund.setThisYear(stringToDouble(array[4]));
            fund.setLastOneYear(stringToDouble(array[9]));
            fund.setLastTwoYear(stringToDouble(array[10]));
            fund.setLastThreeYear(stringToDouble(array[11]));
            Double lastTwoYear = fund.getLastTwoYear();
            Double lastTwoYearAvg = lastTwoYear != null ? divide(lastTwoYear, 2.0) : 0;
            Double lastThreeYear = fund.getLastThreeYear();
            Double lastThreeYearAvg = lastThreeYear != null ? divide(lastThreeYear, 3.0) : 0;
            fund.setLastTwoYearAvg(lastTwoYearAvg);
            fund.setLastThreeYearAvg(lastThreeYearAvg);
            list.add(fund);
        }
        // 批量设置基金详情
        batchSetFundDetail(list);
        return list;
    }

    /**
     * 除法
     *
     * @param numberA
     * @param numberB
     * @return
     */
    private static Double divide(Double numberA, Double numberB) {
        if (numberA == null || numberB == null) {
            return BigDecimal.ZERO.doubleValue();
        }
        return BigDecimal.valueOf(numberA).divide(BigDecimal.valueOf(numberB), BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 字符串转小数
     *
     * @param str
     * @return
     */
    private static Double stringToDouble(String str) {
        Double result = null;
        try {
            if (!str.equals("--")) {
                result = Double.valueOf(str);
            }
        } catch (NumberFormatException e) {
        }
        return result;
    }

    /**
     * 批量设置基金详情
     *
     * @param fundList
     * @return
     */
    private static List<Fund> batchSetFundDetail(List<Fund> fundList) {
        // 结果集
        List<Fund> result = new ArrayList<>();

        // 总记录数
        int count = fundList.size();
        // 每页记录数
        int size = 10;
        // 页数
        int page = count / size;
        // 不能被整除时，页数需要增加1
        if (count % size != 0) {
            page++;
        }

        // 线程池
        ExecutorService executorService = Executors.newFixedThreadPool(50);
        // 多线程任务列表
        List<FutureTask<List<Fund>>> taskList = new ArrayList<>();
        // 索引
        int index = 0;
        for (int i = 0; i < page; i++) {
            int end = index + size;
            // 避免下标越界
            end = end > count ? count : end;
            // 截取List指定范围内容放入线程中
            FundDetailCallable callable = new FundDetailCallable(fundList.subList(index, end));
            FutureTask<List<Fund>> futureTask = new FutureTask<>(callable);
            taskList.add(futureTask);
            executorService.submit(futureTask);
            index += size;
            try {
                Thread.sleep(240);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        for (FutureTask<List<Fund>> futureTask : taskList) {
            try {
                result.addAll(futureTask.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        executorService.shutdown();

        return result;
    }

    /**
     * 获取基金详情
     *
     * @param code
     * @return
     */
    public static FundDetail getFundDetail(String code) {
        FundDetail fundDetail = new FundDetail();
        Document document = null;
        // 最大请求数
        int maxRequestNumber = 5;
        int count = maxRequestNumber;
        while (count > 0) {
            try {
                document = Jsoup.connect(String.format("http://fund.eastmoney.com/%s.html", code)).get();
                break;
            } catch (IOException e) {
                log.error("第{}次 - 重新获取基金编号为{}的基金详情！", (maxRequestNumber - count + 1), code);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
            count--;
        }
        if (document == null) {
            return fundDetail;
        }
        Double fundScale = getFundScale(document);
        String establishDay = getEstablishDay(document);
        List<String> topTenStockPosition = getTopTenStockPosition(document);
        fundDetail.setScale(fundScale);
        fundDetail.setEstablishDay(establishDay);
        fundDetail.setEstablishDays(getEstablishDays(establishDay));
        fundDetail.setTopTenStockPosition(topTenStockPosition);
        fundDetail.setIncumbentFundManager(getIncumbentFundManager(document));
        return fundDetail;
    }

    /**
     * 前十股票持仓
     *
     * @param document
     * @return
     */
    private static List<String> getTopTenStockPosition(Document document) {
        Elements elements = document.select("#position_shares > div.poptableWrap > table > tbody > tr > td.alignLeft > a");
        if (elements == null) {
            return new ArrayList<>();
        }
        return elements.eachText();
    }

    /**
     * 获取基金规模
     *
     * @param document
     * @return
     */
    private static Double getFundScale(Document document) {
        Double fundScale = null;
        Elements elements = document.select(".infoOfFund > table > tbody > tr");
        if (elements == null) {
            return fundScale;
        }
        JXDocument jxDocument = JXDocument.create(elements);
        JXNode jxNode = jxDocument.selNOne("//tr[1]/td[2]/text()");
        if (jxNode != null) {
            String fundScaleStr = jxNode.asString();
            fundScaleStr = fundScaleStr.replace("：", "");
            fundScaleStr = fundScaleStr.substring(0, fundScaleStr.lastIndexOf("亿元"));
            fundScale = stringToDouble(fundScaleStr);
        }
        return fundScale;
    }

    /**
     * 获取成立日期
     *
     * @param document
     * @return
     */
    private static String getEstablishDay(Document document) {
        Elements elements = document.select(".infoOfFund > table > tbody > tr");
        if (elements == null) {
            return null;
        }
        JXDocument jxDocument = JXDocument.create(elements);
        JXNode jxNode = jxDocument.selNOne("//tr[2]/td[1]/text()");
        return jxNode != null ? jxNode.asString().replace("：", "") : null;
    }

    /**
     * 获取成立天数
     *
     * @param establishDay 成立日
     * @return
     */
    private static Integer getEstablishDays(String establishDay) {
        Integer days = 0;
        if (establishDay == null) {
            return days;
        }
        days = (int) ChronoUnit.DAYS.between(dateStrToLocalDate(establishDay), LocalDate.now());
        return days;
    }

    /**
     * 获取现任基金经理
     *
     * @param document
     * @return
     */
    private static FundManager getIncumbentFundManager(Document document) {
        FundManager fundManager = new FundManager();
        Elements elements = document.select("#fundManagerTab > div:nth-child(1) > table > tbody > tr.noBorder");
        if (elements == null) {
            return fundManager;
        }
        JXDocument jxDocument = JXDocument.create(elements);
        // 名称
        JXNode nameNode = jxDocument.selNOne("//tr/td[2]/a/text()");
        fundManager.setName(nameNode.asString());
        // 任职起始日期
        JXNode takeOfficeStartDateNote = jxDocument.selNOne("//tr/td[1]/text()");
        String takeOfficeStartDate = takeOfficeStartDateNote.asString();
        takeOfficeStartDate = takeOfficeStartDate.substring(0, takeOfficeStartDate.indexOf("~"));
        fundManager.setTakeOfficeStartDate(takeOfficeStartDate);
        // 任职天数
        int takeOfficeDays = (int) ChronoUnit.DAYS.between(dateStrToLocalDate(takeOfficeStartDate), LocalDate.now());
        fundManager.setTakeOfficeDays(takeOfficeDays);
        // 任职年份（年）
        Double takeOfficeYear = takeOfficeDays >= 365 ? divide(Double.valueOf(takeOfficeDays), 365.0) : 0;
        fundManager.setTakeOfficeYear(takeOfficeYear);
        // 任期回报（%）
        JXNode termOfficeReturnNode = jxDocument.selNOne("//tr/td[4]/text()");
        String termOfficeReturnStr = termOfficeReturnNode.asString();
        termOfficeReturnStr = termOfficeReturnStr.replace("%", "");
        Double termOfficeReturn = stringToDouble(termOfficeReturnStr);
        fundManager.setTermOfficeReturn(termOfficeReturn);
        // 任期年回报（%）
        Double termOfficeYearReturn = 0.0;
        if (termOfficeReturn != null) {
            if (takeOfficeYear.compareTo(Double.valueOf(0)) == 0) {
                termOfficeYearReturn = termOfficeReturn;
            } else {
                termOfficeYearReturn = divide(termOfficeReturn, takeOfficeYear);
            }
        }
        fundManager.setTermOfficeYearReturn(termOfficeYearReturn);
        return fundManager;
    }

    /**
     * 日期字符串转LocalDate类型
     *
     * @param dateStr
     * @return
     */
    private static LocalDate dateStrToLocalDate(String dateStr) {
        try {
            return DateUtils.parseDate(dateStr, DateFormatUtils.ISO_DATE_FORMAT.getPattern()).toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}
